﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using Indigo.Quibbler.Properties;

namespace Indigo.Quibbler.Data {
    public enum ContentType {
        News, Photo, Video
    }
    public class Parser {
        #region Constructor

        private Parser() {
        }
        #endregion

        #region Local Private Variables
        private static XmlDocument _docNews;
        private static XmlDocument _docPhotos;
        private static XmlDocument _docVideos;
        private static string _sourceNews = string.Empty;
        private static string _sourcePhoto = string.Empty;
        private static string _sourceVideos = string.Empty;
        private static Parser _parser;
        #endregion

        #region Public Static Methods
        public static bool RefreshXmlDoc(ContentType contentType, string source) {
            try {
                switch (contentType) {
                    case ContentType.News:
                        if (_sourceNews.Equals(source, StringComparison.OrdinalIgnoreCase) == false) return false;
                        _docNews = new XmlDocument();
                        _docNews.Load(source);
                        break;
                    case ContentType.Photo:
                        if (_sourcePhoto.Equals(source, StringComparison.OrdinalIgnoreCase) == false) return false;
                        _docPhotos = new XmlDocument();
                        _docPhotos.Load(source);
                        break;
                    case ContentType.Video:
                        if (_sourceVideos.Equals(source, StringComparison.OrdinalIgnoreCase) == false) return false;
                        _docVideos = new XmlDocument();
                        _docVideos.Load(source);
                        break;
                }
            } catch { return false; }
            return true;
        }
        public static Parser GetInstance(ContentType contentType, string source) {
            //Double Null Checking
            if (_parser == null)
                lock (_sourceNews)
                    if (_parser == null) {
                        _parser = new Parser();
                    }
            switch (contentType) {
                case ContentType.News:
                    if (_docNews != null) break;
                    _docNews = new XmlDocument(); _docNews.Load(source); _sourceNews = source;
                    break;
                case ContentType.Photo:
                    if (_docPhotos != null) break;
                    _docPhotos = new XmlDocument(); _docPhotos.Load(source); _sourcePhoto = source;
                    break;
                case ContentType.Video:
                    if (_docVideos != null) break;
                    _docVideos = new XmlDocument(); _docVideos.Load(source); _sourceVideos = source;
                    break;
                default:
                    break;
            }
            return _parser;
        }
        #endregion

        #region News Articles
        /// <summary>
        /// Returns List of News Categories
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <returns>List of News Categories</returns>
        public List<string> CategoryInfo(string source, string category) {
            List<string> categoryList = new List<string>();
            XmlDocument doc = Validate(ContentType.News, source); //Validate the Inputs
            XmlNodeList channelList = doc.SelectNodes("pub/channel");
            if (channelList == null) throw new XmlException("pub/channel section not found.");
            //if category is NULL; return the ROOT CATEGORY
            if (string.IsNullOrEmpty(category)) {
                var query = from channelNode in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                            where channelNode.SelectSingleNode("category").InnerText.Contains("/") == false
                            select channelNode.SelectSingleNode("category");
                foreach (var item in query) { categoryList.Add(item.InnerText); }
            } else {
                //Get only one level down sub categories
                int count = 1;
                count = category.Split('/').Length;
                var query = from channelNode in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                            where channelNode.SelectSingleNode("category").InnerText.StartsWith(category)
                            && channelNode.SelectSingleNode("category").InnerText.Split('/').Length - 1 == count
                            select channelNode.SelectSingleNode("category");
                foreach (var item in query) { categoryList.Add(item.InnerText); }
            }
            return categoryList;
        }
        /// <summary>
        /// Returns the News Articles for given Category
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <param name="category">Category for which News are required</param>
        /// <returns>List of News Articles</returns>
        public List<NewsArticle> ArticleList(string source, string category) {
            List<NewsArticle> articleList = new List<NewsArticle>();
            string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase) + "\\";
            if (appPath.Contains("file:\\")) appPath = appPath.Replace("file:\\", string.Empty);
            XmlDocument doc = Validate(ContentType.News, source); //Validate the Inputs
            if (string.IsNullOrEmpty(category)) throw new ArgumentNullException("Category provided is Invalid.");
            XmlNodeList channelList = doc.SelectNodes("pub/channel");
            if (channelList == null) throw new XmlException("pub/channel section not found.");
            var query = from channelNode in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                        where channelNode.SelectSingleNode("category").InnerText.Equals(category, StringComparison.OrdinalIgnoreCase)
                        select channelNode;
            XmlNode channelNodeItem = query.SingleOrDefault();
            if (channelNodeItem == null) return articleList;
            foreach (XmlNode itemNode in channelNodeItem.SelectNodes("item")) {
                NewsArticle article = new NewsArticle();
                //Article Basic Details
                article.Headline = itemNode.SelectSingleNode("title").InnerText;
                article.Id = itemNode.SelectSingleNode("id").InnerText;
                article.PubDate = itemNode.SelectSingleNode("pubDate").InnerText;
                article.SyncTime = Convert.ToInt64(itemNode.SelectSingleNode("syncTime").InnerText);
                article.ServerSyncTime = Convert.ToInt64(itemNode.SelectSingleNode("serverSyncTime").InnerText);
                if (itemNode.SelectSingleNode("rating") != null)
                    article.Rating = itemNode.SelectSingleNode("rating").InnerText;
                article.Keywords = new List<string>();
                if (itemNode.SelectSingleNode("keywords") != null && itemNode.SelectSingleNode("keywords").SelectNodes("keyword") != null) {
                    foreach (XmlElement keyword in itemNode.SelectSingleNode("keywords").SelectNodes("keyword")) {
                        article.Keywords.Add(keyword.InnerText);
                    }
                }
                XmlNode articleNode = itemNode.SelectSingleNode("article");
                if (articleNode == null) continue;
                //Paragraphs
                foreach (XmlNode para in articleNode.SelectNodes("paragraph"))
                    if (string.IsNullOrEmpty(para.InnerText) == false) {
                        string originalText = para.InnerText;
                        originalText = originalText.Replace("|B_S|Critic's Rating:|B_E|", String.Empty);
                        originalText = originalText.Replace("|B_S|Critic Rating:|B_E|", String.Empty);
                        if (originalText.Trim() == String.Empty)
                            continue;
                        if (originalText.Contains("|B_S|")) {
                            string text = _CleanedUp(originalText);
                            article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.Bold });
                        } else if (originalText.Contains("|I_S|")) {
                            string text = _CleanedUp(originalText);
                            article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.Italic });
                        } else {
                            string text = _CleanedUp(originalText);
                            article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.None });
                        }
                    }
                //Images
                foreach (XmlNode imageNode in itemNode.SelectNodes("slideShow/image")) {
                    Image image = new Image();
                    if (imageNode.SelectSingleNode("url") != null) image.Url = imageNode.SelectSingleNode("url").InnerText;
                    if (imageNode.SelectSingleNode("caption ") != null) image.Caption = imageNode.SelectSingleNode("caption ").InnerText;
                    if (string.IsNullOrEmpty(image.Url) == false) {
                        // image.Url = appPath + image.Url;
                        image.Url = image.Url;
                        article.Images.Add(image);
                    }
                }
                if (article.Paragraphs.Count > 0) {
                    if (itemNode.SelectSingleNode("caption") != null && string.IsNullOrEmpty(itemNode.SelectSingleNode("caption").InnerText) == false) article.SubHeadline = itemNode.SelectSingleNode("caption").InnerText;
                    else article.SubHeadline = article.Paragraphs[0].Text;
                    articleList.Add(article);
                }
            }
            if (Settings.Default.NewsPrefernece.Equals("Show All", StringComparison.OrdinalIgnoreCase) == false) {
                return articleList.OrderByDescending(ele => ele.ServerSyncTime).Take(Settings.Default.MaxLatestNews).ToList();
            } else { return articleList.OrderByDescending(ele => ele.ServerSyncTime).ToList(); }
        }
        #endregion News Articles

        #region Photo Gallery
        /// <summary>
        /// Returns the Photo Gallery for given Category
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <param name="category">Category for which Photos are required</param>
        /// <returns>List of Photo Gallery</returns>
        public List<NewsArticle> PhotoArticleList(string source, string category) {
            return ContentArticleList(ContentType.Photo, source, category);
        }
        /// <summary>
        /// Returns List of Photo Categories
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <returns>List of Photo Categories</returns>
        public List<string> PhotoCategoryInfo(string source) {
            return ContentCategoryInfo(ContentType.Photo, source);
        }
        #endregion Photo Gallery

        #region Video Gallery
        /// <summary>
        /// Returns the Video Gallery for given Category
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <param name="category">Category for which Videos are required</param>
        /// <returns>List of Video Gallery</returns>
        public List<NewsArticle> VideoArticleList(string source, string category) {
            return ContentArticleList(ContentType.Video, source, category);
        }
        /// <summary>
        /// Returns List of Video Categories
        /// </summary>
        /// <param name="source">XML File Path</param>
        /// <returns>List of Video Categories</returns>
        public List<string> VideoCategoryInfo(string source) {
            return ContentCategoryInfo(ContentType.Video, source);
        }
        #endregion

        #region Search Provider Parser
        //For Search Purposes
        public static List<NewsArticle> Articles(string[] source, bool isImageSearch) {
            List<NewsArticle> result = new List<NewsArticle>();
            string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase) + "\\";
            if (appPath.Contains("file:\\")) appPath = appPath.Replace("file:\\", string.Empty);

            foreach (string curString in source) {
                try {
                    XmlDocument document = new XmlDocument();
                    document.LoadXml(curString);
                    XmlNode itemNode = document.SelectSingleNode("item");

                    NewsArticle article = new NewsArticle();
                    //Article Basic Details
                    if (itemNode.SelectSingleNode("category") != null)
                        article.Category = itemNode.SelectSingleNode("category").InnerText;
                    article.Headline = itemNode.SelectSingleNode("title").InnerText;
                    article.Id = itemNode.SelectSingleNode("id").InnerText;
                    article.PubDate = itemNode.SelectSingleNode("pubDate").InnerText;
                    XmlNode articleNode = itemNode.SelectSingleNode("article");
                    if (articleNode == null) continue;
                    //Paragraphs
                    foreach (XmlNode para in articleNode.SelectNodes("paragraph"))
                        if (string.IsNullOrEmpty(para.InnerText) == false) {
                            string originalText = para.InnerText;
                            if (originalText.Contains("|B_S|")) {
                                string text = _CleanedUp(originalText);
                                article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.Bold });
                            } else if (originalText.Contains("|I_S|")) {
                                string text = _CleanedUp(originalText);
                                article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.Italic });
                            } else {
                                string text = _CleanedUp(originalText);
                                article.Paragraphs.Add(new ArticleParagraph() { Text = text, Style = ParagraphStyle.None });
                            }
                        }
                    if (articleNode.SelectSingleNode("rating") != null)
                        article.Rating = articleNode.SelectSingleNode("rating").InnerText;
                    article.Keywords = new List<string>();
                    if (articleNode.SelectSingleNode("keywords") != null && articleNode.SelectSingleNode("keywords").SelectNodes("keyword") != null) {
                        foreach (XmlElement keyword in articleNode.SelectSingleNode("keywords").SelectNodes("keyword")) {
                            article.Keywords.Add(keyword.InnerText);
                        }
                    }
                    //Images
                    foreach (XmlNode imageNode in itemNode.SelectNodes("slideShow/image")) {
                        Image image = new Image();
                        if (imageNode.SelectSingleNode("url") != null) image.Url = imageNode.SelectSingleNode("url").InnerText;
                        if (imageNode.SelectSingleNode("caption ") != null) image.Caption = imageNode.SelectSingleNode("caption ").InnerText;
                        if (string.IsNullOrEmpty(image.Url) == false) {
                            // image.Url = appPath + image.Url;
                            image.Url = image.Url;
                            article.Images.Add(image);
                        }
                    }
                    if (isImageSearch) {
                        var value = from cur in result where cur.Id == article.Id select cur.Id;
                        if (article != null && value.Count() == 0)
                            result.Add(article);
                    } else {
                        if (article.Paragraphs.Count > 0) {
                            if (itemNode.SelectSingleNode("caption") != null && string.IsNullOrEmpty(itemNode.SelectSingleNode("caption").InnerText) == false) article.SubHeadline = itemNode.SelectSingleNode("caption").InnerText;
                            else article.SubHeadline = article.Paragraphs[0].Text;
                            var value = from cur in result where cur.Id == article.Id select cur.Id;
                            if (article != null && article.Paragraphs.Count > 0 && value.Count() == 0)
                                result.Add(article);
                        }
                    }
                } catch (Exception ex) {
                    Utilities.Log("Error in parsing search result", ex);
                }
            }
            return result;
        }
        #endregion

        #region Public Get City List
        /// <summary>
        /// Returns the List of cities which needs to be shown in Setting Window
        /// </summary>
        /// <param name="source">Source File Path</param>
        /// <returns>Dictionary of City Name with corresponding Category Name</returns>
        public Dictionary<string, string> City(string source) {
            Dictionary<string, string> cityDictionary = new Dictionary<string, string>();
            List<string> cityList = this.CategoryInfo(source, "City");
            var query = from c in cityList
                        orderby c ascending
                        select c;
            foreach (var item in query.ToList()) {
                if (cityDictionary.Keys.Contains(item)) continue;
                cityDictionary.Add(item.Replace("City/", string.Empty), item);
            }
            return cityDictionary;
        }
        #endregion

        #region Helper Methods
        private XmlDocument Validate(ContentType contentType, string source) {
            if (string.IsNullOrEmpty(source) || File.Exists(source) == false) throw new ArgumentException(string.Format("Invalid Source set. Source:{0}", source));
            XmlDocument doc = new XmlDocument();
            do {
                try {
                    switch (contentType) {
                        case ContentType.News:
                            if (_docNews == null) doc.Load(source);
                            else doc = _docNews;
                            break;
                        case ContentType.Photo:
                            if (_docPhotos == null) doc.Load(source);
                            else doc = _docPhotos;
                            break;
                        case ContentType.Video:
                            if (_docVideos == null) doc.Load(source);
                            else doc = _docVideos;
                            break;
                        default:
                            break;
                    }

                    break;
                } catch { System.Threading.Thread.Sleep(100); }
            } while (true);
            return doc;
        }
        private static string _CleanedUp(string originalText) {
            originalText = originalText.Replace("|B_S|", "");
            originalText = originalText.Replace("|B_E|", "");
            originalText = originalText.Replace("|I_S|", "");
            originalText = originalText.Replace("|I_E|", "");
            return originalText;
        }
        private List<string> ContentCategoryInfo(ContentType contentType, string source) {
            List<string> categoryList = new List<string>();
            XmlDocument doc = Validate(contentType, source); //Validate the Inputs
            XmlNodeList channelList = doc.SelectNodes("pub/channel");
            if (channelList == null) throw new XmlException("pub/channel section not found.");
            //if category is NULL; return the ROOT CATEGORY
            var query = from channelNode in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                        where channelNode.SelectSingleNode("category").InnerText.Split('/').Length - 1 == 1
                        select channelNode.SelectSingleNode("category");
            foreach (var item in query) {
                if (contentType == ContentType.Video)
                    categoryList.Add(item.InnerText.Replace("VideoGallery/", string.Empty));
                else if (contentType == ContentType.Photo)
                    categoryList.Add(item.InnerText.Replace("PhotoGallery/", string.Empty));
            }
            return categoryList;
        }
        private List<NewsArticle> ContentArticleList(ContentType contentType, string source, string category) {
            string appPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase) + "\\";
            if (appPath.Contains("file:\\")) appPath = appPath.Replace("file:\\", string.Empty);
            XmlDocument doc = Validate(contentType, source); //Validate the Inputs
            if (string.IsNullOrEmpty(category)) throw new ArgumentNullException("Category provided is Invalid.");
            XmlNodeList channelList = doc.SelectNodes("pub/channel");
            if (channelList == null) throw new XmlException("pub/channel section not found.");
            string searchCat = category;
            switch (contentType) {
                case ContentType.Photo:
                    searchCat = "photogallery/" + category.ToLower();
                    break;
                case ContentType.Video:
                    searchCat = "videogallery/" + category.ToLower();
                    break;
            }

            var query = from channelNode in doc.DocumentElement.ChildNodes.Cast<XmlNode>()
                        where channelNode.SelectSingleNode("category").InnerText.ToLower().Equals(searchCat)
                        select channelNode;
            Dictionary<string, NewsArticle> contentGalleryDictionary = new Dictionary<string, NewsArticle>();
            foreach (XmlNode channelNode in query) {
                foreach (XmlNode itemNode in channelNode.SelectNodes("item")) {
                    if (itemNode.SelectSingleNode("caption") != null) {
                        if (contentGalleryDictionary.Keys.Contains(String.Format(itemNode.SelectSingleNode("title").InnerText + itemNode.SelectSingleNode("caption").InnerText)))
                            continue;
                    } else
                        if (contentGalleryDictionary.Keys.Contains(String.Format(itemNode.SelectSingleNode("title").InnerText + String.Empty)))
                            continue;

                    NewsArticle article = new NewsArticle();
                    article.Headline = itemNode.SelectSingleNode("title").InnerText;
                    if (itemNode.SelectSingleNode("caption") != null && !String.IsNullOrEmpty(itemNode.SelectSingleNode("caption").InnerText))
                        article.SubHeadline = itemNode.SelectSingleNode("caption").InnerText;
                    else
                        article.SubHeadline = String.Empty;
                    article.Id = itemNode.SelectSingleNode("guid").InnerText;
                    if (itemNode.SelectSingleNode("rating") != null)
                        article.Rating = itemNode.SelectSingleNode("rating").InnerText;
                    article.SyncTime = Convert.ToInt64(itemNode.SelectSingleNode("syncTime").InnerText);
                    article.ServerSyncTime = Convert.ToInt64(itemNode.SelectSingleNode("serverSyncTime").InnerText);
                    article.Keywords = new List<string>();
                    if (itemNode.SelectSingleNode("keywords") != null && itemNode.SelectSingleNode("keywords").SelectNodes("keyword") != null) {
                        foreach (XmlElement keyword in itemNode.SelectSingleNode("keywords").SelectNodes("keyword")) {
                            article.Keywords.Add(keyword.InnerText);
                        }
                    }
                    //Images
                    foreach (XmlNode imageNode in itemNode.SelectNodes("slideShow/image")) {
                        Image image = new Image();
                        if (imageNode.SelectSingleNode("url") != null) image.Url = imageNode.SelectSingleNode("url").InnerText;
                        if (imageNode.SelectSingleNode("caption ") != null) image.Caption = imageNode.SelectSingleNode("caption ").InnerText;
                        if (string.IsNullOrEmpty(image.Url) == false) {
                            //image.Url = appPath + image.Url;
                            image.Url = image.Url;
                            article.Images.Add(image);
                        }
                    }
                    //Videos
                    if (contentType == ContentType.Video) {
                        if (itemNode.SelectSingleNode("videos") != null) {
                            foreach (XmlNode videoNode in itemNode.SelectNodes("videos/video")) {
                                Video video = new Video();
                                if (videoNode.SelectSingleNode("url") != null) video.Url = videoNode.SelectSingleNode("url").InnerText;
                                if (videoNode.SelectSingleNode("caption ") != null) video.Caption = videoNode.SelectSingleNode("caption ").InnerText;
                                article.Videos.Add(video);
                            }
                        }
                    }
                    contentGalleryDictionary.Add(String.Format(article.Headline + article.SubHeadline), article);
                }
            }
            if (Settings.Default.NewsPrefernece.Equals("Show All", StringComparison.OrdinalIgnoreCase) == false) {
                return contentGalleryDictionary.Values.OrderByDescending(ele => ele.ServerSyncTime).Take(Settings.Default.MaxLatestNews).ToList();
            } else { return contentGalleryDictionary.Values.OrderByDescending(ele => ele.ServerSyncTime).ToList(); }
        }
        #endregion
    }
}
